/*
 * Created by IntelliJ IDEA.
 * User: rousseau
 * Date: May 12, 2002
 * Time: 3:14:55 PM
 * To change template for new class use
 * Code Style | Class Templates options (Tools | IDE Options).
 */
package org.jfract.abstractBeans;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Vector;

import org.jfract.util.CompletionListener;
import org.jfract.util.FractalComputer;
import org.jfract.util.Location;
import org.jfract.util.PictureProcessor;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;

import com.smartnsoft.fractory.R;

/**
 * this is an abstract class for the beans that implements FractalComputer.
 */
public abstract class AbstractFractalBean
    implements Serializable, FractalComputer
{

  private static final long serialVersionUID = -7444479720471834795L;

  protected String name;

  // protected native int[] compute(float[] values, int maxIter, int interColorLength);
  protected PictureProcessor processor;

  protected CompletionListener completionListener;

  protected double sigmaFactor = 1;

  protected double sigma = 1;

  protected int[] resultColor = null;

  protected int totalPixels = 0;

  private boolean includeInterAfter;

  private int width;
  
  //@Willy you cqn plqy with this param if you get memory issues inside JNI
  private int maxArraySize = 90000;

  private float[] values = null;

  protected int type;

  public float[] getValues()
  {
    return values;
  }

  public void setValues(float[] values)
  {
    this.values = values;
  }

  public int getMaxArraySize()
  {
    return maxArraySize;
  }

  public void setMaxArraySize(int maxArraySize)
  {
    this.maxArraySize = maxArraySize;
  }
  
  
  /**
   * This is used for dynamic display square of 8/8 then 4/4 etc ....
   */
  private int[] refreshLevels = new int[] { 8, 4, 2, 1 };

  //private int[] refreshLevels = new int[] {8};

  public void setProcessor(PictureProcessor processor)
  {
    this.processor = processor;
  }

  public int[] getRefreshLevels()
  {
    return refreshLevels;
  }

  public void setRefreshLevels(int[] refreshLevels)
  {
    this.refreshLevels = refreshLevels;
  }

  public int getWidth()
  {
    return width;
  }

  public void setWidth(int width)
  {
    this.width = width;
  }

  public int getHeight()
  {
    return height;
  }

  public void setHeight(int height)
  {
    this.height = height;
  }

  private int height;

  public CompletionListener getCompletionListener()
  {
    return completionListener;
  }

  public void setCompletionListener(CompletionListener completionListener)
  {
    this.completionListener = completionListener;
  }

  public boolean isIncludeInterAfter()
  {
    return includeInterAfter;
  }

  public void setIncludeInterAfter(boolean includeInterAfter)
  {
    this.includeInterAfter = includeInterAfter;
  }

  public int getTotalPixels()
  {
    return totalPixels;
  }

  public void setTotalPixels(int totalPixels)
  {
    this.totalPixels = totalPixels;
    resultColor = new int[totalPixels];
  }

  public int[] getResultColor()
  {
    return resultColor;
  }

  public void setResultColorSize(int size)
  {
    resultColor = new int[size];
  }

  public void setResultColor(int[] resultColor)
  {
    this.resultColor = resultColor;
  }

  public double getSigmaFactor()
  {
    return sigmaFactor;
  }

  public void setSigmaFactor(double sigmaFactor)
  {
    this.sigmaFactor = sigmaFactor;
    setLocation(getLocation());
  }

  public double getSigma()
  {
    return sigma;
  }

  public Vector getInternColorVector()
  {
    return internColorVector;
  }

  protected Vector internColorVector = new Vector();

  public void setConfiguration(Context context, int type)
  {
    switch (type)
    {
    case 0:
      this.expression = context.getResources().getString(R.string.Mandlebrot_default_expression);
      this.name = context.getResources().getString(R.string.Mandlebrot_default_name);
      break;
    case 1:
      this.expression = context.getResources().getString(R.string.Mandlebrot_cubic_expression);
      this.name = context.getResources().getString(R.string.Mandlebrot_cubic_name);
      break;
    case 2:
      this.expression = context.getResources().getString(R.string.Mandlebrot_quartic_expression);
      this.name = context.getResources().getString(R.string.Mandlebrot_quartic_name);
      break;
    case 3:
      this.expression = context.getResources().getString(R.string.Mandlebrot_quintic_expression);
      this.name = context.getResources().getString(R.string.Mandlebrot_quintic_name);
      break;
    case 4:
      this.expression = context.getResources().getString(R.string.Mandlebrot_sextic_expression);
      this.name = context.getResources().getString(R.string.Mandlebrot_sextic_name);
      break;
    default:
      this.expression = context.getResources().getString(R.string.Mandlebrot_default_expression);
      this.name = context.getResources().getString(R.string.Mandlebrot_default_name);
      break;
    }

  }

  public String getExpression()
  {
    return this.expression;
  }

  public int getType()
  {
    return type;
  }

  public void setType(int type)
  {
    this.type = type;
  }

  public String getName()
  {
    return this.name;
  }

  /**
   * get the processor to manage it
   * 
   * @return
   */
  public PictureProcessor getProcessor()
  {
    return processor;
  }

  /** pause the picture processor */
  public void pause()
  {
    if (processor != null)
      processor.pause();
  }

  /** stop the picture processor */
  public void stop()
  {
    if (processor != null)
      processor.stopThread();
  }

  public void resume()
  {
    if (processor != null)
      processor.notifyThread();
  }

  /**
   * give the default location in term of x, y, zoom
   */
  public Location getDefaultLocation()
  {
    return computer.getDefaultLocation();
  }

  public FractalComputer getComputer()
  {
    return computer;
  }

  public void setComputer(FractalComputer computer)
  {
    this.computer = computer;
  }

  private FractalComputer computer = (FractalComputer) this;

  public void setLocation(Location location)
  {
    this.location = location;
    this.sigma = .5 * sigmaFactor / location.zoom;
  }

  public Location getLocation()
  {
    return location;
  }

  private Location location = this.getDefaultLocation();

  public double getZoomFactor()
  {
    return zoomFactor;
  }

  public void setZoomFactor(double zoomFactor)
  {
    this.zoomFactor = zoomFactor;
  }

  private double zoomFactor = 2;

  public void setCompletion(CompletionListener completion)
  {
    this.completion = completion;
  }

  private transient CompletionListener completion;

  public float getXmin()
  {
    return xmin;
  }

  public float getYmin()
  {
    return ymin;
  }

  public float getDeltay()
  {
    return deltay;
  }

  public float getDeltax()
  {
    return deltax;
  }

  /**
   * The buffered image to paint the graphics
   */
  private transient Bitmap img = null;

  public void setIter(int iter)
  {
    this.iter = iter;
    redefineColors(iter);
  }

  /**
   * the zoom
   */
  private float zoom = (float) .2;

  public float getZoom()
  {
    return zoom;
  }

  public void setZoom(float zoom)
  {
    this.zoom = zoom;
  }

  public int getIter()
  {
    return iter;
  }

  protected int iter = 255;

  public int[] getColor()
  {
    return color;
  }

  public void setColor(int[] color)
  {
    this.color = color;
  }

  public int[] getInternColor()
  {
    return internColor;
  }

  public int[] getAppendColorAndInternal()
  {
    if (color != null && internColor != null)
    {
      int[] appendedColor = new int[getColor().length + internColor.length];
      System.arraycopy(color, 0, appendedColor, 0, getColor().length);
      System.arraycopy(internColor, 0, appendedColor, getColor().length, internColor.length);
      return appendedColor;
    }
    return null;
  }

  public void setInternColor(int[] internColor)
  {
    this.internColor = internColor;
  }

  /**
   * The table of color
   */
  protected int color[] = COLORRED;

  /**
   * The red color palette
   */
  protected static int[] COLORRED;

  /**
   * The green color palette
   */
  protected static int[] COLORGREEN;

  /**
   * inverted palette
   */
  public int[] COLORWHITE;

  public int[] COLORWHITEBRUT;

  public int[] COLORBLACK_TO_RED;

  public int[] COLOR_WHITE_BLACK;

  protected int internColor[] = COLORBLACK_TO_RED;

  /**
   * The deltax value representing the incrementation of x value for a diff of one pixel
   */
  private float deltax;

  /**
   * same as delatx for y axe
   */
  private float deltay;

  /**
   * the extreme left x value of the graph
   */
  private float xmin;

  /**
   * same for y axes
   */
  private float ymin;

  protected String expression;

  private void redefineColors(int iter)
  {
    COLORRED = new int[iter];
    COLORGREEN = new int[iter];
    for (int i = 0; i < iter - 1; i++)
    {
      COLORRED[i] = Color.rgb(255 * i / iter, 0, 0);
    }
    COLORRED[iter - 1] = Color.rgb(0, 0, 255);

    for (int i = 0; i < iter - 1; i++)
    {
      COLORGREEN[i] = Color.rgb(0, 255 * i / iter, 0);
    }
    COLORGREEN[iter - 1] = Color.rgb(0, 0, 255);
    COLORWHITE = new int[iter];
    for (int i = 0; i < iter - 1; i++)
    {
      COLORWHITE[i] = Color.rgb(255 - (255 * i / iter), 255 - (255 * i / iter), 255);
    }
    COLORWHITE[iter - 1] = Color.rgb(0, 0, 255);

    COLORBLACK_TO_RED = new int[iter];
    for (int i = 0; i < iter - 1; i++)
    {
      COLORBLACK_TO_RED[i] = Color.rgb(255 * i / iter, 0, 0);
    }
    COLORBLACK_TO_RED[iter - 1] = Color.rgb(0, 0, 255);
    COLOR_WHITE_BLACK = new int[iter];
    for (int i = 0; i < iter; i++)
    {
      COLOR_WHITE_BLACK[i] = Color.rgb(255 * i / iter, 255 * i / iter, 255 * i / iter);
    }
    COLOR_WHITE_BLACK[iter - 1] = Color.rgb(255, 255, 255);
    // setInternColor(COLORBLACK_TO_RED);
  }

  /**
   * The constructor
   */
  public AbstractFractalBean()
  {
    redefineColors(getIter());
    setLocation(getDefaultLocation());
  }

  /**
   * set the color table
   */
  public void setPalette(int[] color)
  {
    this.color = color;
  }

  /**
   * The method used to compute the deltax and deltay as double used to draw the graphics
   */
  public void computeDelta(int width, int height)
  {
    float current_x = getLocation().x;
    float current_y = getLocation().y;
    float current_zoom = getLocation().zoom;

    deltax = 1 / (current_zoom * width);
    deltay = 1 / (current_zoom * height);
    xmin = current_x - ((float) .5 * (1 / current_zoom));
    ymin = current_y - ((float) .5 * (1 / current_zoom));
  }

  public void setExpression(String expression)
      throws IllegalArgumentException
  {
    this.expression = expression;

  }

  public void compute()
  {

    int totalPixelSize = (int) values.length / 2;
    int[] alreadyComputed = new int[totalPixelSize];
    Arrays.fill(alreadyComputed, -1);

    // int[] result = new int[getWidth() * getHeight()];
    int[] resultIter = new int[getWidth() * getHeight()];
    ;
    int cut = getMaxArraySize() / getWidth();
    for (int refreshLevel : getRefreshLevels())
    {

      int startHeight = 0;
      int endHeight = Math.min(cut, getHeight());

      while (endHeight <= getHeight() && startHeight < getHeight())
      {
        int partialTotalSize = getWidth() * (endHeight - startHeight);
        float valuePartial[] = new float[partialTotalSize * 2];
        int alreadyComputedPartial[] = new int[partialTotalSize];
        System.arraycopy(values, startHeight * getWidth() * 2, valuePartial, 0, partialTotalSize * 2);
        System.arraycopy(alreadyComputed, startHeight * getWidth(), alreadyComputedPartial, 0, partialTotalSize);
        int resultPartial[] = compute(valuePartial, getIter(), getInternColor().length, alreadyComputedPartial, getCompletionListener(), refreshLevel, getWidth(), getHeight(), startHeight, endHeight, getType());
        int resultIterPartial[] = new int[partialTotalSize];
        alreadyComputedPartial = new int[partialTotalSize];
        System.arraycopy(resultPartial, 0, resultIterPartial, 0, partialTotalSize);
        System.arraycopy(resultPartial, partialTotalSize, alreadyComputedPartial, 0, partialTotalSize);
        synchronized (processor.getThreadLock())
        {
          getCompletionListener().newImageComputed(resultIterPartial, 0, startHeight, getWidth(), endHeight - startHeight, endHeight == getHeight());
        }
        System.arraycopy(resultIterPartial, 0, resultIter, startHeight, resultIterPartial.length);
        System.arraycopy(alreadyComputedPartial, 0, alreadyComputed, startHeight * getWidth(), alreadyComputedPartial.length);
        startHeight += cut;
        endHeight = Math.min(endHeight + cut, getHeight());
      }
    }
  }

  protected native int[] compute(float[] values, int maxIter,
      int interColorLength, int[] alreayComputed, CompletionListener listener,
      int delta, int width, int height, int startHeightIndex,
      int endHeightIndex, int type);

}
